Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

fix: resolve react-refresh/runtime to an absolute path #230

Merged
merged 2 commits into from
Nov 1, 2020

Conversation

merceyz
Copy link
Contributor

@merceyz merceyz commented Oct 23, 2020

What's the problem this PR addresses?

The webpack loader injects a require statement to react-refresh/runtime into the users code without resolving it to an absolute path first.

Read https://yarnpkg.com/advanced/rulebook#packages-should-only-ever-require-what-they-formally-list-in-their-dependencies for more details on hoisting

This currently breaks Yarn PnP support in create-react-app

Why is this a problem?

Imagine for a moment that there is no hoisting, that will give a layout like this:

package.json
node_modules
  react-scripts
    node_modules
      react-refresh
      @pmmmwh/react-refresh-webpack-plugin
src
  index.jsx

Now @pmmmwh/react-refresh-webpack-plugin injects require('react-refresh/runtime') into /src/index.js which webpack can't find so it fails, with this change it's injected as require('/node_modules/react-scripts/node_modules/react-refresh/runtime') which webpack can find

How did you fix it?

require.resolve the import to react-refresh/runtime to make sure the same version is always used

@pmmmwh
Copy link
Owner

pmmmwh commented Oct 26, 2020

I'm not sure if I understand this correctly - but we currently already mark react-refresh as a peerDependency, which actually satisfies the "packages should only ever require what they formally marks as dependencies" part. I'm not sure if we fall into the part of "configuration generator" (like eslint plugins) where we should just mark react-refresh as a "defaulted-peer-dependency".

I'm quite sure that this actual change won't cause much harm in effect, but I just want to understand the situation a bit more cause I think it is actually CRA that falls into the "configuration generator" category but not us.

@merceyz
Copy link
Contributor Author

merceyz commented Oct 26, 2020

but we currently already mark react-refresh as a peerDependency, which actually satisfies the "packages should only ever require what they formally marks as dependencies" part

Sort of, before this PR it didn't actually require it, whichever file it was injected into did, resolving it up front like this makes sure that it uses the version provided to it through the peerDependency.

I'm quite sure that this actual change won't cause much harm in effect, but I just want to understand the situation a bit more cause I think it is actually CRA that falls into the "configuration generator" category but not us.

Imagine for a moment that there is no hoisting, that will give a layout like this:

package.json
node_modules
  react-scripts
    node_modules
      react-refresh
      @pmmmwh/react-refresh-webpack-plugin
src
  index.jsx

Now @pmmmwh/react-refresh-webpack-plugin injects require('react-refresh/runtime') into /src/index.js which webpack can't find so it fails, with this change it's injected as require('/node_modules/react-scripts/node_modules/react-refresh/runtime') which webpack can find

@arcanis
Copy link

arcanis commented Oct 26, 2020

Hiya 👋 (note that @merceyz is a Yarn maintainer as well and has plenty of context on hoisting, dependencies, etc 😃)

I'm not sure if I understand this correctly - but we currently already mark react-refresh as a peerDependency, which actually satisfies the "packages should only ever require what they formally marks as dependencies" part.

You do, and as a result it's perfectly fine if you require react-refresh! The problem is that, in the present case, react-refresh-webpack-plugin isn't the (only?) one doing the require call. Since you're literally injecting the following into the transformed user code, from Webpack's perspective, the user code is the one that depends on react-refresh, and since it isn't listed as a dependency there, it's dangerously vulnerable to hoisting problems:

import {...} from 'react-refresh/runtime';

On PnP installs this translates into a hard error, whereas on npm installs this would silently "work", but there would be no guarantee as to the version of react-refresh that would end up being used (since the hoisting is unpredictable). It would usually work, until you add a separate workspace that would use a more recent version of react-refresh-webpack-plugin with an incompatible react-refresh, and suddenly your old application would start to crash.

The fix @merceyz made, that calls require.resolve on react-refresh, ensures that you store the fully resolved path into the transformed sources rather than the bare identifier. By doing this, you ensure that Webpack will now from the get go where to find react-refresh, and won't try to resolve it relative to the source being processed.

@pmmmwh
Copy link
Owner

pmmmwh commented Oct 28, 2020

Thanks for the clarifications (to both of you)! I'll merge this tomorrow and hopefully push out a patch version soon.

@pmmmwh pmmmwh merged commit 6dc1e42 into pmmmwh:main Nov 1, 2020
@merceyz merceyz deleted the merceyz/resolve-refresh branch November 1, 2020 22:44
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

3 participants